﻿using System;
using System.Net;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;

namespace Range_Pinger
{
    public partial class PingIPRange : Form
    {
        ClosedHosts closedHosts = new ClosedHosts();
        OpenHosts openHosts = new OpenHosts();

        uint startIP, endIP, currentIP;
        Object ipIteratorLock = new Object();
        int count = 0;
        int open = 0;
        int closed = 0;

        System.Threading.CancellationTokenSource _tokenSource = new System.Threading.CancellationTokenSource();
        System.Threading.CancellationToken _cancelToken;
        
        public PingIPRange()
        {
            InitializeComponent();
        }

        void tmrPingInterval_Tick(object sender, EventArgs e)
        {
            if (txtTo.Text == string.Empty)
            {
                //Regarding default - http://msdn.microsoft.com/en-us/library/xwth0h0d(v=vs.80).aspx
                Ping(UIntToIPAddress(startIP), default(System.Threading.CancellationToken));
            }
            else
            {
                if (currentIP > endIP)
                {
                    tmrPingInterval.Stop();
                    txtFrom.Enabled = true;
                    txtTo.Enabled = true;
                }
                else
                {
                    //Regarding default - http://msdn.microsoft.com/en-us/library/xwth0h0d(v=vs.80).aspx
                    Ping(UIntToIPAddress(currentIP), null);
                    tmrPingInterval.Interval = (int)nudInterval.Value;
                }
            }
        }

        static uint str2ip(string ip)
        {
            string[] numbers = ip.Split('.');

            uint x1 = (uint)(Convert.ToByte(numbers[0]) << 24);
            uint x2 = (uint)(Convert.ToByte(numbers[1]) << 16);
            uint x3 = (uint)(Convert.ToByte(numbers[2]) << 8);
            uint x4 = (uint)(Convert.ToByte(numbers[3]));

            return x1 + x2 + x3 + x4;
        }

        static string ip2str(uint ip)
        {
            string s1 = ((ip & 0xff000000) >> 24).ToString() + ".";
            string s2 = ((ip & 0x00ff0000) >> 16).ToString() + ".";
            string s3 = ((ip & 0x0000ff00) >> 8).ToString() + ".";
            string s4 = (ip & 0x000000ff).ToString();

            return s1 + s2 + s3 + s4;
        }

        private void btnPing_Click(object sender, EventArgs e)
        {
            if (txtFrom.Text != string.Empty)
            {
                chkMultithreaded.Enabled = false;
                txtFrom.Enabled = false;
                txtTo.Enabled = false;
                btnPing.Enabled = false;
                tsslPingCount.Text = string.Empty;
                count = 0;
                open = 0;
                closed = 0;
                tmrPingInterval.Interval = (int)nudInterval.Value;
                pbProgress.Value = 0;                

                try
                {
                    startIP = str2ip(txtFrom.Text);
                    if (txtTo.Text != string.Empty)
                    {
                        endIP = str2ip(txtTo.Text);
                    }
                    currentIP = startIP;
                    
                    if (chkMultithreaded.Checked)
                    {
                        _tokenSource = new System.Threading.CancellationTokenSource();
                        _cancelToken = _tokenSource.Token;
                        for (currentIP = startIP; currentIP <= endIP; currentIP++)
                        {
                            //Fetch the next address by converting from unit to String to IPAddress
                            IPAddress nextAddress = UIntToIPAddress(currentIP);

                            //Create and Start a new async task using a nice lambda expression that can take our cancel
                            //  token right away.
                            //  Lambda primer: http://msdn.microsoft.com/en-us/library/bb397687.aspx
                            System.Threading.Tasks.Task.Factory.StartNew(() => Ping(nextAddress, _cancelToken), _cancelToken);

                            //Only queue 4 addresses at a time
                            if (currentIP - startIP == 4) break;
                        }
                    }
                    else { tmrPingInterval.Enabled = true; }
                    
                }
                catch(Exception ex)
                {
                    MessageBox.Show("Input must be in IP format: 100.100.100.100", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    txtFrom.Enabled = true;
                    txtTo.Enabled = true;
                }
            }
            else MessageBox.Show("IP field cannot be empty!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private IPAddress UIntToIPAddress(uint p_In)
        {
            byte[] chunks = BitConverter.GetBytes(p_In);
            Array.Reverse(chunks);
            return new IPAddress(chunks);
        }

        private uint IPAddressToUInt(IPAddress p_In)
        {
            byte[] chunks = p_In.GetAddressBytes();
            Array.Reverse(chunks);
            return BitConverter.ToUInt32(chunks,0);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            if (chkMultithreaded.Checked) _tokenSource.Cancel();

            chkMultithreaded.Enabled = true;
            txtFrom.Enabled = true;
            txtTo.Enabled = true;
            btnPing.Enabled = true;
            tmrPingInterval.Stop();
            pbProgress.Value = 0;
        }

        private void Ping(IPAddress address, System.Threading.CancellationToken? cancelToken)
        {
            Ping pingSender = new Ping();
            PingOptions options = new PingOptions();
            
            string data = string.Empty;
            int dataCounter = 0;

            options.Ttl = (int)nudTTL.Value;

            if (cbDontFragment.Checked)
            {
                options.DontFragment = true;
            }
            else
            {
                options.DontFragment = false;
            }

            for (int i = 0; i < nudData.Value; i++)
            {
                dataCounter++;
                if (dataCounter == 10) dataCounter = 0;
                data += dataCounter.ToString();
            }

            byte[] buffer = Encoding.ASCII.GetBytes(data);

            try
            {
                //Now we start doing the heavy work, so its to our benefit to 
                //  start testing the cancellation token in case we need to bail out
                //  We may not have passed in a cancel token though (from the pinging
                //  triggered from the form timer) so we need to test for that.
                //  Cancel primer: http://msdn.microsoft.com/en-us/library/dd997396.aspx
                //
                //  CancelToken is also a Struct, which means we can't directly null it
                //  so we have to wrap it as Nullable<>, hence the HasValue and Value calls. 
                //  See here: http://stackoverflow.com/questions/270334/c-sharp-compiler-throws-invalid-arguments-error-why

                //Ping the address assigned to this invokation
                if (cancelToken.HasValue) cancelToken.Value.ThrowIfCancellationRequested();
                PingReply reply = pingSender.Send(address, (int)nudTimeout.Value, buffer, options);

                //Create the new result set with all the data we need to post to the form
                if (cancelToken.HasValue) cancelToken.Value.ThrowIfCancellationRequested();
                PingResult result = new PingResult(reply, address, options, DateTime.Now, nudData.Value);

                //And safely post it to the form
                if (cancelToken.HasValue) cancelToken.Value.ThrowIfCancellationRequested();
                PostPingResult(result);
            }
            catch (Exception ex)
            {
                if (cancelToken.HasValue) cancelToken.Value.ThrowIfCancellationRequested();
                PostPingException(ex);
            }
            finally
            {
                lock (ipIteratorLock)
                {
                    currentIP++;

                    if (currentIP <= endIP)
                    {
                        if (chkMultithreaded.Checked)
                        {
                            //Fetch the next address by converting from unit to String to IPAddress
                            IPAddress nextAddress = UIntToIPAddress(currentIP);

                            //Create and Start a new async task using a nice lambda expression that can take our cancel
                            //  token right away.
                            //  Lambda primer: http://msdn.microsoft.com/en-us/library/bb397687.aspx
                            System.Threading.Tasks.Task.Factory.StartNew(() => Ping(nextAddress, _cancelToken), _cancelToken);
                        }
                    }
                }
            }
        }

        private delegate void PostPingResultDelegate(PingResult result);
        private void PostPingResult(PingResult result)
        {
             if (this.InvokeRequired)
            {
                PostPingResultDelegate d = new PostPingResultDelegate(PostPingResult);
                this.Invoke(d, result);
            }
            else
            {
                count++;
                pbProgress.Value = (int) Math.Floor(((double)count / (endIP - startIP + 1)) * 100);
 
                string text;

                if (result.Reply.Status == IPStatus.Success)
                {
                    open++;

                    text = string.Format("{0}  [OPEN]  Host: {1}  Round Trip: {2} ms  Fragment: {3}  TTL: {4}  Buffer: {5} {6}",
                        result.StartTime.ToString(), 
                        result.RemoteAddress, 
                        result.Reply.RoundtripTime, 
                        !result.Options.DontFragment, 
                        result.Options.Ttl, 
                        result.BufferSize, 
                        Environment.NewLine);
                    
                    openHosts.txtOpenHosts.AppendText(text);
                }
                else
                {
                    closed++;

                    text = string.Format("{0}  [CLOSED]  Host: {1}  Round Trip: --- ms  Fragment: {2}  TTL: {3}  Buffer: {4} {5}",
                        result.StartTime.ToString(),
                        result.RemoteAddress,
                        !result.Options.DontFragment,
                        result.Options.Ttl,
                        result.BufferSize,
                        Environment.NewLine);

                    closedHosts.txtClosedHosts.AppendText(text);
                }

                txtDisplay.AppendText(text);
                tsslPingCount.Text = string.Format("Hosts Scanned: {0}  Open Hosts: {1}  Closed Hosts: {2}", count, open, closed);
            }
        }

        private delegate void PostPingExceptionDelegate(Exception ex);
        private void PostPingException(Exception ex)
        {
            if (this.InvokeRequired)
            {
                PostPingExceptionDelegate d = new PostPingExceptionDelegate(PostPingException);
                this.Invoke(d, ex);
            }
            else
            {
                txtDisplay.AppendText(ex.Message + Environment.NewLine);
            }
        }

        private void tsmiSaveClosedHosts_Click_1(object sender, EventArgs e)
        {
            if (closedHosts.txtClosedHosts.Text != string.Empty)
            {
                sfdSaveLog.FileName = "Closed Hosts " + txtFrom.Text + ".txt";
                sfdSaveLog.Filter = "Text Documents|*.txt|All Files|*.*";
                sfdSaveLog.Title = "Save Log to Text File.";
                sfdSaveLog.OverwritePrompt = true;

                if (sfdSaveLog.ShowDialog() == DialogResult.OK)
                {
                    System.IO.File.WriteAllText(sfdSaveLog.FileName, closedHosts.txtClosedHosts.Text);
                }
            }
        }

        private void tsmiSaveOpenHosts_Click_1(object sender, EventArgs e)
        {
            if (openHosts.txtOpenHosts.Text != string.Empty)
            {
                sfdSaveLog.FileName = "Open Hosts " + txtFrom.Text + ".txt";
                sfdSaveLog.Filter = "Text Documents|*.txt|All Files|*.*";
                sfdSaveLog.Title = "Save Log to Text File.";
                sfdSaveLog.OverwritePrompt = true;

                if (sfdSaveLog.ShowDialog() == DialogResult.OK)
                {
                    System.IO.File.WriteAllText(sfdSaveLog.FileName, openHosts.txtOpenHosts.Text);
                }
            }
        }

        private void tsmiSaveEntireLog_Click(object sender, EventArgs e)
        {
            if (txtDisplay.Text != string.Empty)
            {
                sfdSaveLog.FileName = txtFrom.Text + ".txt";
                sfdSaveLog.Filter = "Text Documents|*.txt|All Files|*.*";
                sfdSaveLog.Title = "Save Log to Text File.";
                sfdSaveLog.OverwritePrompt = true;

                if (sfdSaveLog.ShowDialog() == DialogResult.OK)
                {
                    System.IO.File.WriteAllText(sfdSaveLog.FileName, txtDisplay.Text);
                }
            }
        }

        private void tsmiHelp_Click(object sender, EventArgs e)
        {
            Help help = new Help();
            help.Show();
        }

        private void tsmiAbout_Click(object sender, EventArgs e)
        {
            About about = new About();
            about.ShowDialog();
        }

        private void tsmiDNS_Click(object sender, EventArgs e)
        {
            ConvertAddress convert = new ConvertAddress();
            convert.Show();
        }

        private void tsmiOpenHosts_Click(object sender, EventArgs e)
        {
            openHosts.Show();
        }

        private void tsmiClosedHosts_Click(object sender, EventArgs e)
        {
            closedHosts.Show();
        }

        private void tsmiExit_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void chkMultithreaded_CheckedChanged(object sender, EventArgs e)
        {
            label2.Visible = !chkMultithreaded.Checked;
            nudInterval.Visible = !chkMultithreaded.Checked; 
        }
    }
}
